/**
 * @file Thread.cpp
 *
 *  @date 2009-05-26
 *  @author hauleth
 */

#include <Bust/Threads/Thread.hpp>
#include "PrivateData.hpp"

#include <pthread.h>
#include <cassert>

namespace bust {

bool Thread::_isInitialized = false;

int ThreadPrivateData::nextId = 0;
pthread_key_t ThreadPrivateData::s_tls_key;

void thread_cleanup_handler( void *arg ) {
	ThreadCleanupStruct *tcs = static_cast< ThreadCleanupStruct * > ( arg );
	* ( tcs->runflag ) = false;
}

class ThreadPrivateActions {
		friend class Thread;
	private:
		static void *StartThread( void *data ) throw(std::runtime_error) {
			Thread *thread = static_cast< Thread * > ( data );
			ThreadPrivateData *pd =
					static_cast< ThreadPrivateData * > ( thread->_data );
			ThreadCleanupStruct tcs;
			tcs.thread = thread;
			tcs.runflag = &pd->isRunning;
			int status = pthread_setspecific( ThreadPrivateData::s_tls_key,
					thread );
			if( status != 0 )
				throw std::runtime_error(
						"Error occured while creating thread." );
			//assert( status == 0 );
			pthread_cleanup_push(thread_cleanup_handler, &tcs);
					pd->isRunning = true;
					thread->run();
					pd->isRunning = false;
					pthread_cleanup_pop(0);
			return 0;
		}
};

struct ThreadInfo {
		pthread_t* tid;
		volatile bool isRunning;
		volatile bool isCanceled;
};

Thread::Thread() {
	if( !_isInitialized )
		init();

	ThreadPrivateData *pd = new ThreadPrivateData();
	pd->idSet = false;
	pd->isRunning = false;
	pd->isCanceled = false;
	pd->uniqueId = pd->nextId;
	pd->nextId++;

	_data = static_cast< void * > ( pd );
}

Thread::~Thread() {
	ThreadInfo* data = static_cast< ThreadInfo* > ( this->_data );
	delete data;
}

void Thread::init() {
	if( _isInitialized )
		return;
	// Allocate a key to be used to access thread local storage
	int status = pthread_key_create( &ThreadPrivateData::s_tls_key, NULL );
	/*if(status != 0)
	 throw std::runtime_error("Error occured while initializing thread.");*/
	assert( status == 0 );
	_isInitialized = true;
}

bool Thread::start() {
	int status;
	pthread_attr_t thread_attr;
	status = pthread_attr_init( &thread_attr );
	if( status != 0 )
		return false;
	ThreadPrivateData *pd = static_cast< ThreadPrivateData * > ( _data );
	if( status != 0 )
		return false;
	status = pthread_create( & ( pd->tid ), &thread_attr,
			ThreadPrivateActions::StartThread, static_cast< void * > ( this ) );
	if( status != 0 )
		return false;
	pd->idSet = true;
	return true;
}

bool Thread::join() {
	void *threadResult = 0; // Dummy var.
	ThreadPrivateData *pd = static_cast< ThreadPrivateData * > ( _data );
	return pthread_join( pd->tid, &threadResult ) == 0;
}

bool Thread::cancel() {
	ThreadPrivateData *pd = static_cast< ThreadPrivateData * > ( _data );
	pd->isCanceled = true;
	int status = pthread_cancel( pd->tid );
	return status == 0;
}

bool Thread::isRunning() {
	ThreadPrivateData *pd = static_cast< ThreadPrivateData * > ( _data );
	return pd->isRunning;
}

bool Thread::isCanceled() {
	ThreadPrivateData *pd = static_cast< ThreadPrivateData * > ( _data );
	return pd->isCanceled;
}

bool Thread::yield() {
	return sched_yield() == 0;
}

}
